﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Loki.Utils
{
    public abstract class TrackedObject : LokiObject, INotifyPropertyChanged, ICentralizedChangeTracking
    {
        #region Constructor
        protected TrackedObject(ICentralizedChangeTracking P_Object)
        {
            _IsDirty = P_Object.IsDirty;
        }

        protected TrackedObject()
        {
        }
        #endregion

        #region Change tracking
        protected void ObservedChanged()
        {
            if (_IsTracking)
            {
                if (!IsDirty)
                {
                    IsDirty = true;
                }
            }
        }

        private bool _IsTracking = true;

        /// <summary>
        /// Notifies the change.
        /// </summary>
        /// <param name="e">Property changed event args.</param>
        protected void NotifyChanged(PropertyChangedEventArgs e)
        {
            if (_IsTracking)
            {
                OnPropertyChanged(e);
            }
        }

        protected void NotifyChangedAndDirty(PropertyChangedEventArgs e)
        {
            if (_IsTracking)
            {
                NotifyChanged(e);
                if (!IsDirty)
                {
                    IsDirty = true;
                }
            }
        }

        private static PropertyChangedEventArgs _IsDirtyChangeArgs = ObservableHelper.CreateArgs<TrackedObject>(x => x.IsDirty);

        private bool _IsDirty = false;

        /// <summary>
        /// Gets or sets a value indicating whether this instance is dirty.
        /// </summary>
        /// <value>Is <c>true</c> if this instance is dirty; otherwise, <c>false</c>.</value>
        public bool IsDirty
        {
            get
            {
                return _IsDirty;
            }

            set
            {
                if (value != _IsDirty)
                {
                    _IsDirty = value;
                    OnStateChanged(EventArgs.Empty);
                    OnPropertyChanged(_IsDirtyChangeArgs);
                }
            }
        }

        /// <summary>
        /// Starts the change tracking.
        /// </summary>
        public virtual void AcceptChanges()
        {
            IsDirty = false;
        }
        #endregion

        #region State changed
        /// <summary>
        /// Occurs when internal state changed.
        /// </summary>
        public event EventHandler StateChanged;

        /// <summary>
        /// Raises the <see cref="StateChanged" /> event.
        /// </summary>
        /// <param name="e">The<see cref="EventArgs" /> object that provides the arguments for the event.</param>
        protected virtual void OnStateChanged(EventArgs e)
        {
            if (StateChanged == null)
            {
                return;
            }
            else
            {
                StateChanged(this, e);
            }
        }
        #endregion

        #region Property changed
        /// <summary>
        /// Se produit lorsqu'une valeur de propriété est modifiée.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the <see cref="PropertyChanged" /> event.
        /// </summary>
        /// <param name="e"><see cref="PropertyChangedEventArgs" /> object that provides the arguments for the event.</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged == null)
            {
                return;
            }
            else
            {
                PropertyChanged(this, e);
            }
        }

        #endregion

        #region Initialize
        /// <summary>
        /// Signale à l'objet que l'initialisation démarre.
        /// </summary>
        public virtual void BeginInit()
        {
            _IsTracking = false;
        }

        /// <summary>
        /// Signale à l'objet que l'initialisation est terminée and accepts changes.
        /// </summary>
        public void EndInit()
        {
            EndInit(true);
        }

        /// <summary>
        /// Signale à l'objet que l'initialisation est terminée.
        /// </summary>
        /// <param name="P_IsStable">If set to <c>true</c>, mark the entity as clean (accept changes).</param>
        public virtual void EndInit(bool P_IsStable)
        {
            _IsTracking = true;
            if (P_IsStable)
            {
                AcceptChanges();
            }
        }
        #endregion

        #region Sub collections
        protected virtual LokiCollection<T> CreateTrackedCollection<T>() where T : ICentralizedChangeTracking
        {
            return CreateTrackedCollection<T>(null);
        }

        protected virtual LokiCollection<T> CreateTrackedCollection<T>(Func<T> P_Adder) where T : ICentralizedChangeTracking
        {
            LokiCollection<T> L_Collection = new LokiCollection<T>(P_Adder);
            try
            {
                L_Collection.ItemChanged += SubCollection_ItemChanged;
                L_Collection.CollectionChanged += SubCollection_CollectionChanged;

                return L_Collection;
            }
            catch
            {
                L_Collection.Dispose();
                throw;
            }
        }

        protected virtual LokiCollection<T> CreateCollection<T>() where T : ICentralizedChangeTracking
        {
            return CreateCollection<T>(null);
        }

        protected virtual LokiCollection<T> CreateCollection<T>(Func<T> P_Adder) where T : ICentralizedChangeTracking
        {
            LokiCollection<T> L_Collection = new LokiCollection<T>(P_Adder);

            return L_Collection;
        }

        protected virtual void SubCollection_CollectionChanged(object P_Sender, NotifyCollectionChangedEventArgs P_EventArgs)
        {
            ObservedChanged();
        }

        protected virtual void SubCollection_ItemChanged<T>(object P_Sender, ItemChangedEventArgs<T> P_EventArgs) where T : ICentralizedChangeTracking
        {
            if (P_EventArgs.Item.IsDirty)
            {
                ObservedChanged();
            }
        }

        protected T CreateTrackedObject<T>() where T : ICentralizedChangeTracking
        {
            return CreateTrackedObject<T>(null);
        }

        protected T CreateTrackedObject<T>(Func<T> P_Adder) where T : ICentralizedChangeTracking
        {
            Func<T> L_RealAdder = P_Adder == null ? ExpressionHelper.New<T>().Compile() : P_Adder;

            T L_Object = L_RealAdder();

            TrackChanges(L_Object);

            return L_Object;
        }

        protected void TrackChanges(ICentralizedChangeTracking P_Observed)
        {
            this.WatchStateChanged(P_Observed, o => o.Tracked_Changed);
        }

        private void Tracked_Changed(object P_Sender, EventArgs e)
        {
            ICentralizedChangeTracking L_Sender = P_Sender as ICentralizedChangeTracking;
            if (L_Sender != null && L_Sender.IsDirty)
            {
                ObservedChanged();
            }
        }
        #endregion
    }
}
